﻿using System;
using System.Diagnostics;
using NLog;
using RaytracerLib.MathLib;
using TomanuExtensions;

namespace RaytracerLib.MathLib
{
    public static class Gamma
    {
        private const double A = 0.055;
        private const double PHI = 12.92;
        private const double GAMMA = 2.4;

        private static double[] s_srgb_to_linear;

        static Gamma()
        {
            s_srgb_to_linear = new double[256];
            for (int i = 0; i < 256; i++)
            {
                s_srgb_to_linear[i] = SRGBToLinear(i / 255.0);
                Debug.Assert(s_srgb_to_linear[i] >= 0);
            }
        }

        public static double LinearToSRGB(double a_value)
        {
            if (a_value <= 0.0031308)
                return a_value * PHI;
            else
                return (1 + A) * Math.Pow(a_value, 1 / GAMMA) - A;
        }

        public static double SRGBToLinear(byte a_value)
        {
            return s_srgb_to_linear[a_value];
        }

        public static double SRGBToLinear(double a_value)
        {
            if (a_value <= 0.04045)
                return a_value / PHI;
            else
                return Math.Pow((a_value + A) / (1 + A), GAMMA);
        }

        /// <summary>
        /// Note: Math.Pow(a_color.R, a_gamma_factor) is slower then this, slighty but measurable.
        /// </summary>
        /// <param name="a_color"></param>
        /// <param name="a_gamma_factor"></param>
        /// <returns></returns>
        public static ColorFloat AdjustGamma(ColorFloat a_color, double a_gamma_factor)
        {
            Debug.Assert(a_gamma_factor >= 0);

            return new ColorFloat(
                Math.Exp(a_gamma_factor * Math.Log(a_color.R)),
                Math.Exp(a_gamma_factor * Math.Log(a_color.G)),
                Math.Exp(a_gamma_factor * Math.Log(a_color.B)));
        }

        public static ColorFloat SRGBToLinear(ColorFloat a_color)
        {
            return new ColorFloat(
                SRGBToLinear(a_color.R),
                SRGBToLinear(a_color.G),
                SRGBToLinear(a_color.B));
        }

        public static ColorFloat SRGBToLinear(double a_r, double a_g, double a_b)
        {
            return new ColorFloat(
                SRGBToLinear(a_r),
                SRGBToLinear(a_g),
                SRGBToLinear(a_b));
        }

        public static ColorFloat LinearToSRGB(ColorFloat a_color)
        {
            return new ColorFloat(
                LinearToSRGB(a_color.R),
                LinearToSRGB(a_color.G),
                LinearToSRGB(a_color.B));
        }

        public static ColorFloat LinearToSRGB(double a_r, double a_g, double a_b)
        {
            return new ColorFloat(
                LinearToSRGB(a_r),
                LinearToSRGB(a_g),
                LinearToSRGB(a_b));
        }
    }
}